﻿using System;

namespace WizardEngine.Core.Specification
{
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;

    /// <summary>
    /// Definition of wizard flow used for final wizard creation. 
    /// </summary>
    /// <remarks>
    /// Specification objects can be resused to create several wizards. You can even extend and
    /// modify the already used specification as wizards do copy everything they need for further operation 
    /// from the specification object during registration time. 
    /// </remarks>
    internal class FlowSpecification<T> : IStepOwner<T>, IFlowSpecification<T> where T : FlowState, new()
    {
        private readonly IDictionary<string, StepSpecification<T>> steps = new Dictionary<string, StepSpecification<T>>();
        private readonly FlowSpecificationValidator<T> validator = new FlowSpecificationValidator<T>();

        /// <summary>
        /// Validates the specification instance.
        /// </summary>
        public bool IsValid
        {
            get
            {
                return this.validator.Validate(this);
            }
        }

        public IStepSpecification<T> InitialStep
        {
            get;
            set;
        }

        /// <summary>
        /// Registers new step specification under the specification.
        /// </summary>
        /// <param name="stepName">Unique name identifing the step. </param>
        /// <returns>Step specification ready for further configuration. </returns>
        public IStepSpecification<T> RegisterStep(string stepName)
        {
            Contract.Assert(!string.IsNullOrEmpty(stepName));
            Contract.Assert(!this.steps.ContainsKey(stepName));

            var step = new StepSpecification<T>(stepName, this);
            this.steps.Add(stepName, step);
            return step;
        }

        /// <summary>
        /// Binds the requested step to the current definition context. 
        /// </summary>
        /// <param name="stepName">Already registered step name. </param>
        /// <returns>Requested step. </returns>
        /// <exception cref="StepNotFound" />
        public IStepSpecification<T> BindStep(string stepName)
        {
            if (!this.steps.ContainsKey(stepName))
            {
                throw new StepNotFound();
                
            }

            return this.steps[stepName];
        }

        /// <summary>
        /// Checks whether given step is possible binding result. 
        /// </summary>
        /// <param name="step">Step specification. </param>
        /// <returns>True if given step can be binded, false otherwise. </returns>
        public bool IsBindable(IStepSpecification<T> step)
        {
            Contract.Assert(step as StepSpecification<T> != null);
            return this.steps.Values.Contains(step as StepSpecification<T>);
        }
    }
}
